package com.hubao.flutter_weight_parser;

import androidx.annotation.NonNull;

import cn.hutool.http.HttpUtil;
import com.hubao.flutter_weight_parser.protocol.ScopedWeightProtocols;
import com.hubao.flutter_weight_parser.protocol.meta.JavaScriptWeightProtocolClass;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * FlutterWeightParserPlugin
 */
public class FlutterWeightParserPlugin implements FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private MethodChannel channel;


    private ScopedWeightProtocols scopedWeightProtocols;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_weight_parser");
        channel.setMethodCallHandler(this);
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        String name = call.argument("name");
        String id = call.argument("id");
        if (call.method.equals("loadProtocols")) {
            String protocolUrl = call.argument("protocolUrl");
            new Thread(() -> {
                String scripts = new String(HttpUtil.downloadBytes(protocolUrl), StandardCharsets.UTF_8);
                new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
                    List<String> protocolsNames = loadProtocols(scripts);
                    result.success(protocolsNames);
                });
            }).start();

            return;
        }

        if (call.method.equals("addAndParseWeight")) {
            int frame = call.argument("frame");
            new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
                result.success(addAndParseWeight(id, name, frame));
            });
            return;
        }
        if (call.method.equals("removeGroup")) {
            result.success(removeGroup(id));
            return;
        }

        if (call.method.equals("destroy")) {
            this.destroy();
            return;
        }

        result.notImplemented();
    }


    public void add(String id, String name, int frame) {
        scopedWeightProtocols.getProtocol(id, name).add(frame);
    }

    public boolean isComplete(String id, String name) {
        return scopedWeightProtocols.getProtocol(id, name).isComplete();

    }

    public String parseWeight(String id, String name) {
        return scopedWeightProtocols.getProtocol(id, name).parseWeight().map(e -> e.toString()).orElse(null);
    }

    public synchronized String addAndParseWeight(String id, String name, int frame) {
        add(id, name, frame);
        if (isComplete(id, name)) {
            return parseWeight(id, name);
        }
        return null;
    }

    public boolean removeGroup(String id) {
        scopedWeightProtocols.removeScope(id);
        return true;
    }


    private void destroy() {
        if (Objects.nonNull(this.scopedWeightProtocols)) {
            this.scopedWeightProtocols.destroy();
        }
        this.scopedWeightProtocols = null;
        Context.exit();
    }

    /**
     * 加载协议描述
     *
     * @param scripts
     * @return
     */
    public List<String> loadProtocols(String scripts) {
        Context globalRhinoContext = Context.enter();
        globalRhinoContext.setOptimizationLevel(-1);
        Scriptable scope = globalRhinoContext.initStandardObjects(null, true);
        globalRhinoContext.evaluateString(scope, scripts, "WeightProtocols.js", 1, null);
        List<JavaScriptWeightProtocolClass> classes = new ArrayList<>();
        Function creator = (Function) scope.get("getProtocolCreator", scope);
        Object module = creator.call(globalRhinoContext, scope, creator, new Object[]{});

        if (module instanceof NativeObject) {
            NativeObject nativeObject = (NativeObject) module;
            for (Object clazz : nativeObject.getIds()) {
                classes.add(new JavaScriptWeightProtocolClass(nativeObject, (String) clazz));
            }
            scopedWeightProtocols = ScopedWeightProtocols.instance(classes, globalRhinoContext, scope);
            return classes.stream().map(JavaScriptWeightProtocolClass::getClazzName).sorted().collect(Collectors.toList());
        }
        return Collections.emptyList();
    }


    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
        scopedWeightProtocols = null;
        this.destroy();
    }
}
